import datetime
now = datetime.datetime.now()
print ("Última versión:")
print (now.strftime("%Y-%m-%d %H:%M:%S"))
import pandas as pd
import numpy as np
import pandas_profiling
from itertools import combinations
from dateutil.parser import parse
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
import plotly.express as px
#datos procesados
datos = pd.read_csv("Datos-procesados_mun2019.csv", encoding='utf-8')
print(datos.dtypes)
datos
#se reordena y eliminan del dataframe las variables que no se usarán
datos_mun = datos.drop(columns=['Coef_Var_PE', 'Var_Prop_PE', 'Autocorr_PE','Area_Km2'])
datos_mun = datos_mun.reindex(columns= ['EDO','NOMBRE_ESTADO','MUN','NOMBRE_MUNICIPIO','NUM_SECC_RUR','LNE','Densidad_LNE','Razon_LNE_PE','TC_LNE_2019','Coef_Var_LNE','Var_Prop_LNE','Autocorr_LNE'])
datos_mun
#resumen estadístico
datos_mun[datos_mun.columns[4:12]].describe()
#verificar si hay valores nulos en datos_mun
datos_mun.isnull().sum()
#se eliminan las filas (municipios) con valores vacíos
datos_mun_limpio = datos_mun.dropna()
datos_mun_limpio = datos_mun_limpio.reset_index(drop=True) #para reordenar el índice del nuevo dataframe
datos_mun_limpio.isnull().sum()
#se separan los indicadores de los metadatos
metadatos = datos_mun_limpio[['EDO', 'NOMBRE_ESTADO', 'MUN', 'NOMBRE_MUNICIPIO']]
datos_mun_limpio_f = datos_mun_limpio[['NUM_SECC_RUR','LNE', 'Densidad_LNE', 'Razon_LNE_PE', 'TC_LNE_2019', 'Coef_Var_LNE', 'Var_Prop_LNE','Autocorr_LNE']]
datos_mun_limpio_f
#Plot correlogram
#(see https://www.machinelearningplus.com/plots/top-50-matplotlib-visualizations-the-master-plots-python/)
plt.figure(figsize=(10,6), dpi= 200)
sns.heatmap(datos_mun_limpio_f.corr(), xticklabels=datos_mun_limpio_f.corr().columns, yticklabels=datos_mun_limpio_f.corr().columns, cmap='RdYlGn', center=0, annot=True)
# fix for mpl bug that cuts off top/bottom of seaborn viz (see https://github.com/mwaskom/seaborn/issues/1773)
b, t = plt.ylim() # discover the values for bottom and top
b += 0.5 # Add 0.5 to the bottom
t -= 0.5 # Subtract 0.5 from the top
plt.ylim(b, t) # update the ylim(bottom, top) values
#se guarda la grafica
plt.savefig('Pyplot_heatmap_ind8.png',dpi=1200)
plt.show()
#pair-plot
#(see https://www.machinelearningplus.com/plots/top-50-matplotlib-visualizations-the-master-plots-python/)
corr1 = sns.pairplot(datos_mun_limpio_f)
corr1.savefig('Pyplot_correlogram_ind8.png',dpi=1200)
from sklearn.preprocessing import StandardScaler
#Se crean listas para separar los tipos de datos en el dataframe
#seleccionamos 6 indicadores
ind7 = ['NUM_SECC_RUR','LNE', 'Densidad_LNE', 'Razon_LNE_PE', 'TC_LNE_2019', 'Coef_Var_LNE', 'Autocorr_LNE']
#seleccionamos 7 indicadores
ind8 = ['NUM_SECC_RUR','LNE', 'Densidad_LNE', 'Razon_LNE_PE', 'TC_LNE_2019', 'Coef_Var_LNE', 'Var_Prop_LNE', 'Autocorr_LNE']
#etiquetas
labels = ['EDO', 'NOMBRE_ESTADO', 'MUN', 'NOMBRE_MUNICIPIO']
#seleccionamos solo los valores de los 7 indicadores (sin Var_Prop_LNE)
x = datos_mun_limpio.loc[:, ind7].values
#seleccionamos solo los valores de los 6 indicadores
x2 = datos_mun_limpio.loc[:, ind8].values
#seleccionamos las labels (no se usará en el k-medias)
y = datos_mun_limpio.loc[:,labels].values
print(x.shape)
print(x2.shape)
print(y.shape)
#para verificar que no haya valores nulos o infinitos en ind7
print(np.any(np.isnan(x)))
print(np.all(np.isfinite(x)))
#para verificar que no haya valores nulos o infinitos en ind8
print(np.any(np.isnan(x2)))
print(np.all(np.isfinite(x2)))
#Estandarizamos los valores de ind7 (normalización)
x = StandardScaler().fit_transform(x)
print(x.shape)
#Estandarizamos los valores de ind8 (normalización)
x2 = StandardScaler().fit_transform(x2)
print(x2.shape)
from sklearn.decomposition import PCA
#calculo de PCA con 3 componentes, y obtención de los valores de CP para cada municipio
pca_3cp = PCA(n_components=3)
pca_ind7 = pca_3cp.fit_transform(x)
#razones de varianza explicada por cada CP
print('Razón de varianza explicada por cada CP (n_components=3): %s'
% str(pca_3cp.explained_variance_ratio_))
#dataframe con los valores de los CP por municipio
df_pca_ind7 = pd.DataFrame(data = pca_ind7
, columns = ['CP1', 'CP2', 'CP3'])
df_pca_ind7
#obtención de los pesos por cada variable
pca_pesos_ind7 = pca_3cp.components_
print(pca_pesos_ind7)
#transformación a un dataframe:
df_pca_pesos_ind7 = pd.DataFrame(pca_pesos_ind7, columns=ind7)
principal_components_pesos = pd.DataFrame(['CP1','CP2','CP3'])
df_pca_pesos_ind7.insert(0, 'Componentes Principales', principal_components_pesos)
#se guarda el último dataframe en un csv
df_pca_pesos_ind7.to_csv(r'Resultados_Mun_PCA_pesos_ind7.csv', index = None)
df_pca_pesos_ind7=df_pca_pesos_ind7.set_index('Componentes Principales')
df_pca_pesos_ind7
plt.figure(figsize=(10,6), dpi= 200)
sns.heatmap(df_pca_pesos_ind7, cmap='RdYlGn', center=0, annot=True)
# fix for mpl bug that cuts off top/bottom of seaborn viz (see https://github.com/mwaskom/seaborn/issues/1773)
b, t = plt.ylim() # discover the values for bottom and top
b += 0.5 # Add 0.5 to the bottom
t -= 0.5 # Subtract 0.5 from the top
plt.ylim(b, t) # update the ylim(bottom, top) values
plt.savefig('Pyplot_PCA_heatmap_ind7.png',dpi=1200)
plt.show()
#calculo de la matriz de covarianza y sus correspondientes eigenvalores y eigenvectores
cov_mat = np.cov(x.T)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
# calculate of individual and cumulative sum of explained variances
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)
# plot explained variances
plt.figure(figsize=(10,6), dpi= 200)
plt.bar(range(1,8), var_exp, alpha=0.5,
align='center', label='varianza explicada individual')
plt.step(range(1,8), cum_var_exp, where='mid',
label='varianza explicada acumulada')
plt.ylabel('Razón de varianza explicada')
plt.xlabel('Índice Componentes Principales')
plt.legend(loc='best')
plt.savefig('Pyplot_PCA_variance_ind7.png',dpi=1200)
plt.show()
#concatenamos los resultados con las labels y target
df_pca_results_ind7 = pd.concat([datos_mun_limpio[labels], df_pca_ind7], axis = 1)
#formamos un np-array con las columnas_pca para la sección de métodos de agrupamiento
columnas_pca = ['CP1', 'CP2', 'CP3']
x_pca = df_pca_results_ind7.loc[:, columnas_pca].values
print(x_pca.shape)
df_pca_results_ind7
from sklearn.decomposition import PCA
#calculo de PCA con 3 componentes, y obtención de los valores de CP para cada municipio
pca_3cp = PCA(n_components=3)
pca_ind8 = pca_3cp.fit_transform(x2)
#razones de varianza explicada por cada CP
print('Razón de varianza explicada por cada CP (n_components=3): %s'
% str(pca_3cp.explained_variance_ratio_))
#dataframe con los valores de los CP por municipio
df_pca_ind8 = pd.DataFrame(data = pca_ind8
, columns = ['CP1', 'CP2', 'CP3'])
df_pca_ind8
#obtención de los pesos por cada variable
pca_pesos_ind8 = pca_3cp.components_
print(pca_pesos_ind8)
#transformación a un dataframe:
df_pca_pesos_ind8 = pd.DataFrame(pca_pesos_ind8, columns=ind8)
principal_components_pesos = pd.DataFrame(['CP1','CP2','CP3'])
df_pca_pesos_ind8.insert(0, 'Componentes Principales', principal_components_pesos)
#se guarda el último dataframe en un csv
df_pca_pesos_ind8.to_csv(r'Resultados_Mun_PCA_pesos_ind8.csv', index = None)
df_pca_pesos_ind8=df_pca_pesos_ind8.set_index('Componentes Principales')
df_pca_pesos_ind8
plt.figure(figsize=(10,6), dpi= 200)
sns.heatmap(df_pca_pesos_ind8, cmap='RdYlGn', center=0, annot=True)
# fix for mpl bug that cuts off top/bottom of seaborn viz (see https://github.com/mwaskom/seaborn/issues/1773)
b, t = plt.ylim() # discover the values for bottom and top
b += 0.5 # Add 0.5 to the bottom
t -= 0.5 # Subtract 0.5 from the top
plt.ylim(b, t) # update the ylim(bottom, top) values
plt.savefig('Pyplot_PCA_heatmap_ind8.png',dpi=1200)
plt.show()
#calculo de la matriz de covarianza y sus correspondientes eigenvalores y eigenvectores
cov_mat = np.cov(x2.T)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
# calculate of individual and cumulative sum of explained variances
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)
# plot explained variances
plt.figure(figsize=(10,6), dpi= 200)
plt.bar(range(1,9), var_exp, alpha=0.5,
align='center', label='varianza explicada individual')
plt.step(range(1,9), cum_var_exp, where='mid',
label='varianza explicada acumulada')
plt.ylabel('Razón de varianza explicada')
plt.xlabel('Índice Componentes Principales')
plt.legend(loc='best')
plt.savefig('Pyplot_PCA_variance_ind8.png',dpi=1200)
plt.show()
#concatenamos los resultados con las labels y target
df_pca_results_ind8 = pd.concat([datos_mun_limpio[labels], df_pca_ind8], axis = 1)
#formamos un np-array con las columnas_pca para la sección de métodos de agrupamiento
columnas_pca = ['CP1', 'CP2', 'CP3']
x2_pca = df_pca_results_ind8.loc[:, columnas_pca].values
print(x2_pca.shape)
df_pca_results_ind8
Gráfica en el espacio CP1, CP2
#Scatter plot (seaborn) PC1 vs PC2
#(see https://www.machinelearningplus.com/plots/top-50-matplotlib-visualizations-the-master-plots-python)
plt.figure(figsize=(16, 10), dpi= 200, facecolor='w', edgecolor='k')
sns.scatterplot(x="CP1", y="CP2",
data=df_pca_results_ind7)
# Set x-axis label
plt.xlabel('CP1 (ICE principal)')
# Set y-axis label
plt.ylabel('CP2 (segundo ICE)')
plt.savefig('Pyplot_PCA_projection_ind7.png',dpi=1200)
Gráfica en el espacio CP1, CP2, CP3
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.lines import Line2D
fig = pyplot.figure(figsize=(16, 10), dpi=200, facecolor='w', edgecolor='k')
ax = Axes3D(fig)
ax.scatter(df_pca_results_ind7['CP1'], df_pca_results_ind7['CP2'], df_pca_results_ind7['CP3'],s=20)
# ax.set_xlim3d(-10, 40)
ax.set_ylim3d(-7.5,7.5)
ax.set_zlim3d(-2,8)
ax.set_xlabel('CP1 (ICE principal)')
ax.set_ylabel('CP2 (segundo ICE)')
ax.set_zlabel('CP3 (tercer ICE)')
elev = 30.0
azim = 160.0
ax.view_init(elev, azim)
plt.savefig('Pyplot_PCA_3dprojection_ind7.png',dpi=1200)
pyplot.show()
Gráfica 3d animada
# import plotly.express as px
# fig = px.scatter_3d(df_pca_results_ind7,
# x='CP1',
# y='CP2',
# z='CP3')
# fig.update_layout(scene = dict(
# xaxis_title='CP1 (ICE principal)',
# yaxis_title='CP2 (segundo ICE)',
# zaxis_title='CP3 (tercer ICE)'),
# legend_orientation="h")
# fig.show()
Gráfica en el espacio CP1, CP2
#Scatter plot (seaborn) PC1 vs PC2
plt.figure(figsize=(16, 10), dpi= 200, facecolor='w', edgecolor='k')
sns.scatterplot(x="CP1", y="CP2",
data=df_pca_results_ind8)
# Set x-axis label
plt.xlabel('CP1 (ICE principal)')
# Set y-axis label
plt.ylabel('CP2 (segundo ICE)')
plt.savefig('Pyplot_PCA_projection_ind8.png',dpi=1200)
Gráfica en el espacio CP1, CP2, CP3
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.lines import Line2D
fig = pyplot.figure(figsize=(16, 10), dpi=200, facecolor='w', edgecolor='k')
ax = Axes3D(fig)
ax.scatter(df_pca_results_ind8['CP1'], df_pca_results_ind8['CP2'], df_pca_results_ind8['CP3'],s=20)
# ax.set_xlim3d(-10, 40)
ax.set_ylim3d(-7.5,7.5)
ax.set_zlim3d(-2,8)
ax.set_xlabel('CP1 (ICE principal)')
ax.set_ylabel('CP2 (segundo ICE)')
ax.set_zlabel('CP3 (tercer ICE)')
elev = 30.0
azim = 160.0
ax.view_init(elev, azim)
plt.savefig('Pyplot_PCA_3dprojection_ind8.png',dpi=1200)
pyplot.show()
Gráfica 3d animada
# import plotly.express as px
# fig = px.scatter_3d(df_pca_results_ind8,
# x='CP1',
# y='CP2',
# z='CP3')
# fig.update_layout(scene = dict(
# xaxis_title='CP1 (ICE principal)',
# yaxis_title='CP2 (segundo ICE)',
# zaxis_title='CP3 (tercer ICE)'),
# legend_orientation="h")
# fig.show()
Cálculo de los silhouette_scores para diferentes números de clusters en Kmeans
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.cm as cm
#Datos (3d) en el espacio de las componentes principales
X=x_pca
#numero de clusters/grupos que serán analizados
range_n_clusters = [2, 3, 4, 5]
for n_clusters in range_n_clusters:
# Create a subplot with 1 row and 2 columns
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
# The 1st subplot is the silhouette plot
# The silhouette coefficient can range from -1, 1
ax1.set_xlim([-1, 1])
# The (n_clusters+1)*10 is for inserting blank space between silhouette
# plots of individual clusters, to demarcate them clearly.
ax1.set_ylim([0, len(X) + (n_clusters + 1) * 10])
# Initialize the clusterer with n_clusters value and a random generator
# seed of 0 for reproducibility.
clusterer = KMeans(n_clusters=n_clusters, random_state=0)
cluster_labels = clusterer.fit_predict(X)
# The silhouette_score gives the average value for all the samples.
# This gives a perspective into the density and separation of the formed clusters
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
# Compute the silhouette scores for each sample
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
# Aggregate the silhouette scores for samples belonging to
# cluster i, and sort them
ith_cluster_silhouette_values = \
sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i) / n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper),
0, ith_cluster_silhouette_values,
facecolor=color, edgecolor=color, alpha=0.7)
# Label the silhouette plots with their cluster numbers at the middle
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
# Compute the new y_lower for next plot
y_lower = y_upper + 10 # 10 for the 0 samples
ax1.set_title("Diagrama de silueta por cada grupo.")
ax1.set_xlabel("Coeficiente de silueta")
ax1.set_ylabel("Grupos")
# The vertical line for average silhouette score of all the values
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([]) # Clear the yaxis labels / ticks
# 2nd Plot showing the actual clusters formed
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1], marker='.', s=30, lw=0, alpha=0.7,
c=colors, edgecolor='k')
# Labeling the clusters
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='o',
c="white", alpha=1, s=200, edgecolor='k')
for i, c in enumerate(centers):
ax2.scatter(c[0], c[1], marker='$%d$' % i, alpha=1,
s=50, edgecolor='k')
ax2.set_title("Visualización de la tipología resultante")
ax2.set_xlabel("CP1 (ICE principal)")
ax2.set_ylabel("CP2 (segundo ICE)")
plt.suptitle(("Análisis de silueta con K-medias. "
"Número de grupos = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.savefig('Pyplot_silhouette_score_ind7_Kmeans_'+str(n_clusters)+'.png',dpi=1200)
#se obtienen las etiquetas predichas por K-medias para la configuración con el mayor
#valor promedio del silhouete score (n=3)
num_clusters_ind7 = 3
clusterer_K = KMeans(n_clusters=num_clusters_ind7, random_state=0)
clusterer_K_labels = clusterer_K.fit_predict(X)
#se obtienen las etiquetas predichas por Kmeans
y_pred_kmeans = clusterer_K_labels
#se guarda el nparray en un dataframe
y_pred_kmeans = pd.DataFrame(y_pred_kmeans, columns=['TCE_municipal'])
#se transforman las etiquetas numéricas a str
etiquetas_grupos = {0:'G0', 1:'G1', 2:'G2'}
y_pred_kmeans['TCE_municipal']=y_pred_kmeans['TCE_municipal'].apply(lambda x: etiquetas_grupos[x])
#concatenamos los resultados de PCA con las etiquetas de Kmeans
df_pca_results_ind7_kmeans = pd.concat([df_pca_results_ind7, y_pred_kmeans], axis = 1)
#se guardan los resultados en un csv
df_pca_results_ind7_kmeans.to_csv(r'Resultados_Mun_PCA_Kmeans_ind7.csv', index = None)
df_pca_results_ind7_kmeans
Cálculo de los silhouette_scores para diferentes números de clusters en Kmeans
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.cm as cm
#Datos (3d) en el espacio de las componentes principales
X=x2_pca
#numero de clusters/grupos que serán analizados
range_n_clusters = [2, 3, 4, 5]
for n_clusters in range_n_clusters:
# Create a subplot with 1 row and 2 columns
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
# The 1st subplot is the silhouette plot
# The silhouette coefficient can range from -1, 1
ax1.set_xlim([-1, 1])
# The (n_clusters+1)*10 is for inserting blank space between silhouette
# plots of individual clusters, to demarcate them clearly.
ax1.set_ylim([0, len(X) + (n_clusters + 1) * 10])
# Initialize the clusterer with n_clusters value and a random generator
# seed of 0 for reproducibility.
clusterer = KMeans(n_clusters=n_clusters, random_state=0)
cluster_labels = clusterer.fit_predict(X)
# The silhouette_score gives the average value for all the samples.
# This gives a perspective into the density and separation of the formed clusters
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
# Compute the silhouette scores for each sample
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
# Aggregate the silhouette scores for samples belonging to
# cluster i, and sort them
ith_cluster_silhouette_values = \
sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i) / n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper),
0, ith_cluster_silhouette_values,
facecolor=color, edgecolor=color, alpha=0.7)
# Label the silhouette plots with their cluster numbers at the middle
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
# Compute the new y_lower for next plot
y_lower = y_upper + 10 # 10 for the 0 samples
ax1.set_title("Diagrama de silueta por cada grupo.")
ax1.set_xlabel("Coeficiente de silueta")
ax1.set_ylabel("Grupos")
# The vertical line for average silhouette score of all the values
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([]) # Clear the yaxis labels / ticks
# 2nd Plot showing the actual clusters formed
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1], marker='.', s=30, lw=0, alpha=0.7,
c=colors, edgecolor='k')
# Labeling the clusters
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='o',
c="white", alpha=1, s=200, edgecolor='k')
for i, c in enumerate(centers):
ax2.scatter(c[0], c[1], marker='$%d$' % i, alpha=1,
s=50, edgecolor='k')
ax2.set_title("Visualización de la tipología resultante")
ax2.set_xlabel("CP1 (ICE principal)")
ax2.set_ylabel("CP2 (segundo ICE)")
plt.suptitle(("Análisis de silueta con K-medias. "
"Número de grupos = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.savefig('Pyplot_silhouette_score_ind8_Kmeans_'+str(n_clusters)+'.png',dpi=1200)
#se obtienen las etiquetas predichas por K-medias para la configuración con el mayor
#valor promedio del silhouete score (n=5)
num_clusters_ind8 = 5
clusterer_K = KMeans(n_clusters=num_clusters_ind8, random_state=0)
clusterer_K_labels = clusterer_K.fit_predict(X)
#se obtienen las etiquetas predichas por Kmeans
y_pred_kmeans = clusterer_K_labels
#se guarda el nparray en un dataframe
y_pred_kmeans = pd.DataFrame(y_pred_kmeans, columns=['TCE_municipal'])
#se transforman las etiquetas numéricas a str (adecuacion para preservar color de anteriores graficas)
etiquetas_grupos = {0:'G0', 1:'G4', 2:'G2', 3:'G3', 4:'G1'}
y_pred_kmeans['TCE_municipal']=y_pred_kmeans['TCE_municipal'].apply(lambda x: etiquetas_grupos[x])
#concatenamos los resultados de PCA con las etiquetas de Kmeans
df_pca_results_ind8_kmeans = pd.concat([df_pca_results_ind8, y_pred_kmeans], axis = 1)
#se guardan los resultados en un csv
df_pca_results_ind8_kmeans.to_csv(r'Resultados_Mun_PCA_Kmeans_ind8.csv', index = None)
df_pca_results_ind8_kmeans
Se utiliza la última iteración guardada de K-medias para asegurar la reproducibilidad de los resultados (recordando que K-medias es un algoritmo estocástico; y por lo tanto, en cada iteración hay variaciones en la asignación de grupos).
#df_pca_results_ind8_kmeans = pd.read_csv("Resultados_Mun_PCA_Kmeans_ind8.csv", dtype={'EDO':int,'MUN':int,'CP1':float, 'CP2':float, 'CP3':float})
df_pca_results_ind8_kmeans
Gráfica en el espacio CP1, CP2
#se define diccionario de colores
color_dict = dict({'G0':'blue',
'G1':'green',
'G2': 'orange',
'G3': 'red',
'G4': 'grey'})
#Scatter plot (seaborn) PC1 vs PC2 (con clustering k-means)
plt.figure(figsize=(16, 10), dpi= 200, facecolor='w', edgecolor='k')
sns.scatterplot(x="CP1", y="CP2",
data=df_pca_results_ind8_kmeans,
hue="TCE_municipal",
palette=color_dict)
# Set x-axis label
plt.xlabel('CP1 (ICE principal)')
# Set y-axis label
plt.ylabel('CP2 (segundo ICE)')
plt.savefig('Pyplot_PCA_Kmeans_ind8.png',dpi=1200)
Gráfica en el espacio CP1, CP2, CP3
#para definir los colores
colores = df_pca_results_ind8_kmeans['TCE_municipal'].apply(lambda x: color_dict[x])
#personalizar leyenda
legend_elements = [Line2D([0], [0], marker='o', markerfacecolor='orange', color='w', label='G2'),
Line2D([0], [0], marker='o', markerfacecolor='green', color='w', label='G1'),
Line2D([0], [0], marker='o', markerfacecolor='blue', color='w', label='G0'),
Line2D([0], [0], marker='o', markerfacecolor='red', color='w', label='G3'),
Line2D([0], [0], marker='o', markerfacecolor='grey', color='w', label='G4')]
fig = pyplot.figure(figsize=(16, 10), dpi=200, facecolor='w', edgecolor='k')
ax = Axes3D(fig)
ax.scatter(df_pca_results_ind8_kmeans['CP1'], df_pca_results_ind8_kmeans['CP2'], df_pca_results_ind8_kmeans['CP3'], c=colores,s=15)
# ax.set_xlim3d(-10, 40)
ax.set_ylim3d(-7.5,7.5)
ax.set_zlim3d(-2,8)
ax.set_xlabel('CP1 (ICE principal)')
ax.set_ylabel('CP2 (segundo ICE)')
ax.set_zlabel('CP3 (tercer ICE)')
elev = 30.0
azim = 160.0
ax.view_init(elev, azim)
ax.legend(title='TCE_municipal', handles=legend_elements, loc='upper right',fontsize='large')
plt.savefig('Pyplot_PCA_3d_Kmeans_ind8.png',dpi=1200)
pyplot.show()
Gráfica 3d animada
#3-d plot con plotly
# fig = px.scatter_3d(df_pca_results_ind8_kmeans, x='CP1', y='CP2', z='CP3',
# color='TCE_municipal',
# color_discrete_map={
# 'G0':'blue',
# 'G1':'green',
# 'G2': 'orange',
# 'G3': 'red',
# 'G4': 'black'})
# fig.update_traces(mode='markers', marker_line_width=1, marker_size=3)
# fig.update_layout(scene = dict(
# xaxis_title='CP1 (ICE principal)',
# yaxis_title='CP2 (segundo ICE)',
# zaxis_title='CP3 (tercer ICE)'),
# legend_orientation="h")
# fig.show()
#metadatos e indicadores de los municipios
datos_mun_limpio
df_pca_results_ind8_kmeans
#unimos los resultados de PCA_Kmeans con los metadatos y los 8 indicadores de los municipios
df_indi_pca_kmeans=pd.merge(datos_mun_limpio, df_pca_results_ind8_kmeans, on=['EDO','NOMBRE_ESTADO','MUN','NOMBRE_MUNICIPIO'], how='inner')
#renombramos algunas columnas_pca
df_indi_pca_kmeans=df_indi_pca_kmeans.rename(columns={"CP1": "CP1 (ICE principal)", "CP2": "CP2 (segundo ICE)", "CP3": "CP3 (tercer ICE)"})
df_indi_pca_kmeans
#sólo tomamos los indicadores y grupos
df_indi_pca_kmeans_f = df_indi_pca_kmeans[['NUM_SECC_RUR','LNE', 'Densidad_LNE', 'Razon_LNE_PE', 'TC_LNE_2019', 'Coef_Var_LNE', 'Var_Prop_LNE', 'Autocorr_LNE', 'TCE_municipal']]
#pair-plot (colores en funcion de grupo)
corr3=sns.pairplot(df_indi_pca_kmeans_f,
hue="TCE_municipal",
palette=color_dict)
#guardar la pair-plot
corr3.savefig('Pyplot_correlogram_ind8-TCE.png',dpi=1200)
Gráfica de caja (por grupos, para el indicador "Coef_Var_LNE")
plt.figure(figsize=(12, 8), dpi= 200, facecolor='w', edgecolor='k')
sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Coef_Var_LNE"], palette=color_dict);
plt.show()
Plotgrid de gráficas de caja para cada indicador e índice de complejidad electoral (por grupo)
sns.set(style="whitegrid")
fig, axes = plt.subplots(6, 2, figsize=(16, 20), dpi= 200)
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["NUM_SECC_RUR"], palette=color_dict, orient='v',
ax=axes[0, 0])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["LNE"], palette=color_dict, orient='v',
ax=axes[0, 1])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Densidad_LNE"], palette=color_dict, orient='v',
ax=axes[1, 0])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Razon_LNE_PE"], palette=color_dict, orient='v',
ax=axes[1, 1])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["TC_LNE_2019"], palette=color_dict, orient='v',
ax=axes[2, 0])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Coef_Var_LNE"], palette=color_dict, orient='v',
ax=axes[2, 1])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Var_Prop_LNE"], palette=color_dict, orient='v',
ax=axes[3, 0])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Autocorr_LNE"], palette=color_dict, orient='v',
ax=axes[3, 1])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["CP1 (ICE principal)"], palette=color_dict, orient='v',
ax=axes[4, 0])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["CP2 (segundo ICE)"], palette=color_dict, orient='v',
ax=axes[4, 1])
ax = sns.boxplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["CP3 (tercer ICE)"], palette=color_dict, orient='v',
ax=axes[5, 0])
fig.delaxes(axes[5][1]) #borrar subplot sin usar
plt.subplots_adjust(hspace=0.3) #dar mas espacio vertical
#se guarda la gráfica
plt.savefig('Pyplot_box-plots-grid.png',dpi=1200)
plt.show()
Gráfica de violín (por grupos, para el indicador "Densidad_LNE")
plt.figure(figsize=(16, 10), dpi= 300, facecolor='w', edgecolor='k')
sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Densidad_LNE"], palette=color_dict);
#se guarda la gráfica
plt.savefig('Pyplot_violin.png',dpi=1200)
plt.show()
Plotgrid de gráficas de violín para cada indicador e índice de complejidad electoral (por grupo)
sns.set(style="whitegrid")
fig, axes = plt.subplots(6, 2, figsize=(16, 20), dpi= 200)
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["NUM_SECC_RUR"], palette=color_dict, orient='v',
ax=axes[0, 0])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["LNE"], palette=color_dict, orient='v',
ax=axes[0, 1])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Densidad_LNE"], palette=color_dict, orient='v',
ax=axes[1, 0])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Razon_LNE_PE"], palette=color_dict, orient='v',
ax=axes[1, 1])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["TC_LNE_2019"], palette=color_dict, orient='v',
ax=axes[2, 0])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Coef_Var_LNE"], palette=color_dict, orient='v',
ax=axes[2, 1])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Var_Prop_LNE"], palette=color_dict, orient='v',
ax=axes[3, 0])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["Autocorr_LNE"], palette=color_dict, orient='v',
ax=axes[3, 1])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["CP1 (ICE principal)"], palette=color_dict, orient='v',
ax=axes[4, 0])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["CP2 (segundo ICE)"], palette=color_dict, orient='v',
ax=axes[4, 1])
ax = sns.violinplot(x=df_indi_pca_kmeans["TCE_municipal"], y=df_indi_pca_kmeans["CP3 (tercer ICE)"], palette=color_dict, orient='v',
ax=axes[5, 0])
fig.delaxes(axes[5][1]) #borrar subplot sin usar
plt.subplots_adjust(hspace=0.3) #dar mas espacio vertical
#se guarda la gráfica
plt.savefig('Pyplot_violin-plot-grid.png',dpi=1200)
plt.show()
Etiquetamos los municipios considerados como atípicos en los ICE (considerando la separación por grupos de la tipología):
#ejemplo:
df_indi_pca_kmeans['CP1 (ICE principal)'].quantile(0.25)
def IQsR_clusters(x):
x_0 = x['TCE_municipal'] == 'G0'
x_g0 = x[x_0]
Q1_g0 = x_g0['CP1 (ICE principal)'].quantile(0.25)
Q3_g0 = x_g0['CP1 (ICE principal)'].quantile(0.75)
IQR_pc1_g0 = Q3_g0 - Q1_g0
Q1_pc2_g0 = x_g0['CP2 (segundo ICE)'].quantile(0.25)
Q3_pc2_g0 = x_g0['CP2 (segundo ICE)'].quantile(0.75)
IQR_pc2_g0 = Q3_pc2_g0 - Q1_pc2_g0
Q1_pc3_g0 = x_g0['CP3 (tercer ICE)'].quantile(0.25)
Q3_pc3_g0 = x_g0['CP3 (tercer ICE)'].quantile(0.75)
IQR_pc3_g0 = Q3_pc3_g0 - Q1_pc3_g0
return IQR_pc1_g0+IQR_pc1_g0;
IQsR_clusters(df_indi_pca_kmeans)
Función para calcular el Q1, Q3 e IQR (calculado por grupos de cluster_Kmeans), y posteriormente clasificar los municipios en tipicos o atipicos en el ICE principal, en el segundo ICE, o en el tercer ICE:
def IQR_clusters(x):
x_0 = df_indi_pca_kmeans['TCE_municipal'] == 'G0'
x_g0 = df_indi_pca_kmeans[x_0]
Q1_g0 = x_g0['CP1 (ICE principal)'].quantile(0.25)
Q3_g0 = x_g0['CP1 (ICE principal)'].quantile(0.75)
IQR_pc1_g0 = Q3_g0 - Q1_g0
Q1_pc2_g0 = x_g0['CP2 (segundo ICE)'].quantile(0.25)
Q3_pc2_g0 = x_g0['CP2 (segundo ICE)'].quantile(0.75)
IQR_pc2_g0 = Q3_pc2_g0 - Q1_pc2_g0
Q1_pc3_g0 = x_g0['CP3 (tercer ICE)'].quantile(0.25)
Q3_pc3_g0 = x_g0['CP3 (tercer ICE)'].quantile(0.75)
IQR_pc3_g0 = Q3_pc3_g0 - Q1_pc3_g0
x_1 = df_indi_pca_kmeans['TCE_municipal'] == 'G1'
x_g1 = df_indi_pca_kmeans[x_1]
Q1_g1 = x_g1['CP1 (ICE principal)'].quantile(0.25)
Q3_g1 = x_g1['CP1 (ICE principal)'].quantile(0.75)
IQR_pc1_g1 = Q3_g1 - Q1_g1
Q1_pc2_g1 = x_g1['CP2 (segundo ICE)'].quantile(0.25)
Q3_pc2_g1 = x_g1['CP2 (segundo ICE)'].quantile(0.75)
IQR_pc2_g1 = Q3_pc2_g1 - Q1_pc2_g1
Q1_pc3_g1 = x_g1['CP3 (tercer ICE)'].quantile(0.25)
Q3_pc3_g1 = x_g1['CP3 (tercer ICE)'].quantile(0.75)
IQR_pc3_g1 = Q3_pc3_g1 - Q1_pc3_g1
x_2 = df_indi_pca_kmeans['TCE_municipal'] == 'G2'
x_g2 = df_indi_pca_kmeans[x_2]
Q1_g2 = x_g2['CP1 (ICE principal)'].quantile(0.25)
Q3_g2 = x_g2['CP1 (ICE principal)'].quantile(0.75)
IQR_pc1_g2 = Q3_g2 - Q1_g2
Q1_pc2_g2 = x_g2['CP2 (segundo ICE)'].quantile(0.25)
Q3_pc2_g2 = x_g2['CP2 (segundo ICE)'].quantile(0.75)
IQR_pc2_g2 = Q3_pc2_g2 - Q1_pc2_g2
Q1_pc3_g2 = x_g2['CP3 (tercer ICE)'].quantile(0.25)
Q3_pc3_g2 = x_g2['CP3 (tercer ICE)'].quantile(0.75)
IQR_pc3_g2 = Q3_pc3_g2 - Q1_pc3_g2
x_3 = df_indi_pca_kmeans['TCE_municipal'] == 'G3'
x_g3 = df_indi_pca_kmeans[x_3]
Q1_g3 = x_g3['CP1 (ICE principal)'].quantile(0.25)
Q3_g3 = x_g3['CP1 (ICE principal)'].quantile(0.75)
IQR_pc1_g3 = Q3_g3 - Q1_g3
Q1_pc2_g3 = x_g3['CP2 (segundo ICE)'].quantile(0.25)
Q3_pc2_g3 = x_g3['CP2 (segundo ICE)'].quantile(0.75)
IQR_pc2_g3 = Q3_pc2_g3 - Q1_pc2_g3
Q1_pc3_g3 = x_g3['CP3 (tercer ICE)'].quantile(0.25)
Q3_pc3_g3 = x_g3['CP3 (tercer ICE)'].quantile(0.75)
IQR_pc3_g3 = Q3_pc3_g3 - Q1_pc3_g3
x_4 = df_indi_pca_kmeans['TCE_municipal'] == 'G4'
x_g4 = df_indi_pca_kmeans[x_4]
Q1_g4 = x_g4['CP1 (ICE principal)'].quantile(0.25)
Q3_g4 = x_g4['CP1 (ICE principal)'].quantile(0.75)
IQR_pc1_g4 = Q3_g4 - Q1_g4
Q1_pc2_g4 = x_g4['CP2 (segundo ICE)'].quantile(0.25)
Q3_pc2_g4 = x_g4['CP2 (segundo ICE)'].quantile(0.75)
IQR_pc2_g4 = Q3_pc2_g4 - Q1_pc2_g4
Q1_pc3_g4 = x_g4['CP3 (tercer ICE)'].quantile(0.25)
Q3_pc3_g4 = x_g4['CP3 (tercer ICE)'].quantile(0.75)
IQR_pc3_g4 = Q3_pc3_g4 - Q1_pc3_g4
if (x['TCE_municipal'] == 'G0'):
if (x['CP1 (ICE principal)'] < Q1_g0-1.5*IQR_pc1_g0) or (x['CP1 (ICE principal)'] > Q3_g0+1.5*IQR_pc1_g0):
return 'Atipica en CP1'
elif (x['CP2 (segundo ICE)'] < Q1_pc2_g0-1.5*IQR_pc2_g0) or (x['CP2 (segundo ICE)'] > Q3_pc2_g0+1.5*IQR_pc2_g0):
return 'Atipica en CP2'
elif (x['CP3 (tercer ICE)'] < Q1_pc3_g0-1.5*IQR_pc3_g0) or (x['CP3 (tercer ICE)'] > Q3_pc3_g0+1.5*IQR_pc3_g0):
return 'Atipica en CP3'
else:
return 'Tipica'
elif (x['TCE_municipal'] == 'G1'):
if (x['CP1 (ICE principal)'] < Q1_g1-1.5*IQR_pc1_g1) or (x['CP1 (ICE principal)'] > Q3_g1+1.5*IQR_pc1_g1):
return 'Atipica en CP1'
elif (x['CP2 (segundo ICE)'] < Q1_pc2_g1-1.5*IQR_pc2_g1) or (x['CP2 (segundo ICE)'] > Q3_pc2_g1+1.5*IQR_pc2_g1):
return 'Atipica en CP2'
elif (x['CP3 (tercer ICE)'] < Q1_pc3_g1-1.5*IQR_pc3_g1) or (x['CP3 (tercer ICE)'] > Q3_pc3_g1+1.5*IQR_pc3_g1):
return 'Atipica en CP3'
else:
return 'Tipica'
elif (x['TCE_municipal'] == 'G2'):
if (x['CP1 (ICE principal)'] < Q1_g2-1.5*IQR_pc1_g2) or (x['CP1 (ICE principal)'] > Q3_g2+1.5*IQR_pc1_g2):
return 'Atipica en CP1'
elif (x['CP2 (segundo ICE)'] < Q1_pc2_g2-1.5*IQR_pc2_g2) or (x['CP2 (segundo ICE)'] > Q3_pc2_g2+1.5*IQR_pc2_g2):
return 'Atipica en CP2'
elif (x['CP3 (tercer ICE)'] < Q1_pc3_g2-1.5*IQR_pc3_g2) or (x['CP3 (tercer ICE)'] > Q3_pc3_g2+1.5*IQR_pc3_g2):
return 'Atipica en CP3'
else:
return 'Tipica'
elif (x['TCE_municipal'] == 'G3'):
if (x['CP1 (ICE principal)'] < Q1_g3-1.5*IQR_pc1_g3) or (x['CP1 (ICE principal)'] > Q3_g3+1.5*IQR_pc1_g3):
return 'Atipica en CP1'
elif (x['CP2 (segundo ICE)'] < Q1_pc2_g3-1.5*IQR_pc2_g3) or (x['CP2 (segundo ICE)'] > Q3_pc2_g3+1.5*IQR_pc2_g3):
return 'Atipica en CP2'
elif (x['CP3 (tercer ICE)'] < Q1_pc3_g3-1.5*IQR_pc3_g3) or (x['CP3 (tercer ICE)'] > Q3_pc3_g3+1.5*IQR_pc3_g3):
return 'Atipica en CP3'
else:
return 'Tipica'
elif (x['TCE_municipal'] == 'G4'):
if (x['CP1 (ICE principal)'] < Q1_g4-1.5*IQR_pc1_g4) or (x['CP1 (ICE principal)'] > Q3_g4+1.5*IQR_pc1_g4):
return 'Atipica en CP1'
elif (x['CP2 (segundo ICE)'] < Q1_pc2_g4-1.5*IQR_pc2_g4) or (x['CP2 (segundo ICE)'] > Q3_pc2_g4+1.5*IQR_pc2_g4):
return 'Atipica en CP2'
elif (x['CP3 (tercer ICE)'] < Q1_pc3_g4-1.5*IQR_pc3_g4) or (x['CP3 (tercer ICE)'] > Q3_pc3_g4+1.5*IQR_pc3_g4):
return 'Atipica en CP3'
else:
return 'Tipica'
else:
return 'nan'
#se aplica la función al último df con los resultados:
df_indi_pca_kmeans['Atipicidad'] = df_indi_pca_kmeans.apply(IQR_clusters, axis = 1)
#se muestra el dataframe:
df_indi_pca_kmeans
#para verificar, imprimimos los valores únicos en la columna Atipicidad.
df_indi_pca_kmeans.Atipicidad.unique()
#imprimimos la frecuencia de los valores únicos en Atipicidad
df_indi_pca_kmeans['Atipicidad'].value_counts()
#imprimimos el número de municipios por grupo de la tipología.
df_indi_pca_kmeans['TCE_municipal'].value_counts()
#se guardan los resultados en un csv
df_indi_pca_kmeans.to_csv(r'Resultados_Mun_Indic_PCA_Kmeans_ind8.csv', index = None)
#se carga el dataframe con la ultima version de los resultados:
resultados_final = pd.read_csv("Resultados_Mun_Indic_PCA_Kmeans_ind8.csv", dtype={'EDO':int,'MUN':int})
resultados_final
#con pandas_profiling se crea una interface para visualizar un resumen del dataframe de los resultados
#resultados_final.profile_report()
#se ordenan y obtienen los 10 municipios por nivel de complejidad (descendente en CP1)
ranking_secc_top10 = resultados_final.sort_values(by='CP1 (ICE principal)', ascending=False).head(10)
#se guarda el ranking
ranking_secc_top10.to_csv(r'Resultados_Mun_ranking-top10_ind8.csv', index = None)
ranking_secc_top10
#se obtienen los municipios de mayor complejidad (mayor CP1) para cada una de las 32 entidades
#ranking_secc_por_edo = resultados_final.groupby(['EDO'], sort=False)['CP1 (ICE principal)'].max()
#ranking_secc_por_edo
idx_rank_secc = resultados_final.groupby(['EDO'])['CP1 (ICE principal)'].transform(max) == resultados_final['CP1 (ICE principal)']
ranking_secc_edo = resultados_final[idx_rank_secc]
ranking_secc_edo.to_csv(r'Resultados_Mun_ranking-edo_ind8.csv', index = None)
ranking_secc_edo
Animación de la proyección de los municipios en el espacio de las CP:
eti=['NOMBRE_ESTADO','NUM_SECC_RUR', 'LNE', 'Densidad_LNE', 'Razon_LNE_PE', 'TC_LNE_2019', 'Coef_Var_LNE', 'Var_Prop_LNE', 'Autocorr_LNE', 'CP1 (ICE principal)', 'CP2 (segundo ICE)', 'CP3 (tercer ICE)', 'TCE_municipal', 'Atipicidad']
#3-d scatter plot con plotly
fig = px.scatter_3d(resultados_final,
x='CP1 (ICE principal)',
y='CP2 (segundo ICE)',
z='CP3 (tercer ICE)',
color='TCE_municipal',
color_discrete_map={
'G0':'blue',
'G1':'green',
'G2': 'orange',
'G3': 'red',
'G4': 'grey'},
hover_name='NOMBRE_MUNICIPIO',
hover_data = eti)
fig.update_traces(mode='markers', marker_line_width=1, marker_size=3)
fig.update_layout(scene = dict(
xaxis_title='CP1 (ICE principal)',
yaxis_title='CP2 (segundo ICE)',
zaxis_title='CP3 (tercer ICE)'),
title="Proyección de los municipios en el espacio de las Componentes Principales",
legend_orientation="h")
fig.show()
#salvar animación en html
fig.write_html("C:/Users/miguel.alvarez/Google Drive/INFOTEC/Proyecto/Code-Data/Mun/Pyplotly_Proyeccion3d_Mun.html")